﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Media;

namespace DeepEarth.Core.Controls
{
    [ContentProperty("Templates")]
    public class TemplateSelector : FrameworkElement
    {
        #region DataType Property - Wishing x:Type was available
        [TypeConverter(typeof(Converters.TypeTypeConverter))]
        public static readonly DependencyProperty DataTypeProperty = DependencyProperty.RegisterAttached("DataType", typeof(Type), typeof(TemplateSelector), null);

        [TypeConverter(typeof(Converters.TypeTypeConverter))]
        public static Type GetDataType(DependencyObject o)
        {
            return o.GetValue(DataTypeProperty) as Type;
        }

        [TypeConverter(typeof(Converters.TypeTypeConverter))]
        public static void SetDataType(DependencyObject o, Type value)
        {
            o.SetValue(DataTypeProperty, value);
        }
        #endregion

        #region PropertyName Property
        public static readonly DependencyProperty PropertyNameProperty = DependencyProperty.RegisterAttached("PropertyName", typeof(string), typeof (TemplateSelector), null);

        public static void SetPropertyName(DependencyObject o, string value)
        {
            o.SetValue(PropertyNameProperty, value);
        }

        public static string GetPropertyName(DependencyObject o)
        {
            return (string) o.GetValue(PropertyNameProperty);
        }
        #endregion

        public List<FrameworkTemplate> Templates { get; private set; }
       
        public TemplateSelector()
        {
            Templates = new List<FrameworkTemplate>();
        }

        #region FindTemplate(), EnumerateTemplates() -- IsTemplate(), EnumerateResources()
        public T FindTemplate<T>(object item) where T : FrameworkTemplate
        {
            var searchType = GetTypeToMatchOn(this, item);
            return BestMatch<T>(searchType, EnumerateTemplates(item).ToList());
        }

        public IEnumerable<FrameworkTemplate> EnumerateTemplates(object item)
        {
            if (item == null)
                yield break;

            var t = GetTypeToMatchOn(this, item);
            foreach (var tpl in Templates.Where(aT => IsTemplate(aT, t)))
                yield return tpl;
            foreach (var tpl in EnumerateTemplates(this, item))
                yield return tpl;
        }

        public static T FindTemplate<T>(DependencyObject depo, object item) where T : FrameworkTemplate
        {
            var searchType = GetTypeToMatchOn(depo, item);
            return BestMatch<T>(searchType, EnumerateTemplates(depo, item).ToList());
        }

        /// <summary>
        /// Retrieves the Type to compare the templates against. This will be the type of the item, or if the PropertyName is specified then the type of that property on the item.
        /// </summary>
        static Type GetTypeToMatchOn(DependencyObject depo, object item)
        {
            var searchType = item.GetType();
            var propName = GetPropertyName(depo);
            if (!string.IsNullOrWhiteSpace((propName)))
            {
                var prop = searchType.GetProperty(propName);
                var value = prop.GetValue(item, null);
                searchType = value == null ? prop.PropertyType : value.GetType();
            }
            return searchType;
        }

        static T BestMatch<T>(Type t, IEnumerable<FrameworkTemplate> matchingTemplates) where T: FrameworkTemplate
        {
            var count = matchingTemplates.Count();
            if (count == 0)
                return null;

            if (count == 1)
                return matchingTemplates.First() as T;

            var exactMatch = matchingTemplates.FirstOrDefault(ft => GetDataType(ft) == t);

            if (exactMatch != null)
                return exactMatch as T;
            return BestMatch<T>(t.BaseType, matchingTemplates);
        }

        public static IEnumerable<FrameworkTemplate> EnumerateTemplates(DependencyObject depo, object item)
        {
            if (item == null)
                yield break;

            var t = GetTypeToMatchOn(depo, item);
            foreach (var res in EnumerateResources(depo))
                foreach (var subres in EnumerateResources(res))
                    foreach (var tpl in subres.Values.OfType<FrameworkTemplate>().Where(aT => IsTemplate(aT, t)))
                        yield return tpl;
        }

        static bool IsTemplate(FrameworkTemplate ft, Type t)
        {
            if (ft == null || t == null)
                return false;
            var ftt = GetDataType(ft);
            if (ftt == null)
                return false;
            return ftt.IsAssignableFrom(t);
        }

        static IEnumerable<ResourceDictionary> EnumerateResources(DependencyObject item)
        {
            while (item != null)
            {
                var fe = item as FrameworkElement;
                if (fe != null)
                    yield return fe.Resources;
                item = VisualTreeHelper.GetParent(item);
            }
            yield return Application.Current.Resources;
        }

        static IEnumerable<ResourceDictionary> EnumerateResources(ResourceDictionary dict)
        {
            if (dict == null)
                yield break;
            yield return dict;
            foreach (var md in dict.MergedDictionaries)
                foreach (var md2 in EnumerateResources(md))
                    yield return md2;
        }
        #endregion
    }
}